#include <stdio.h>
#include <stdlib.h>
#include "Miro.h"
#include "Camera.h"
#include "Image.h"
#include "Scene.h"
#include "Console.h" 
#include "OpenGL.h"

Camera * g_camera = 0;

static bool firstRayTrace = true; 

const float HalfDegToRad = DegToRad/2.0f;

Camera::Camera() :
    m_bgColor(0,0,0),
    m_renderer(RENDER_OPENGL),
    m_eye(0,0,0),
    m_viewDir(0,0,-1),
    m_up(0,1,0),
    m_lookAt(FLT_MAX, FLT_MAX, FLT_MAX),
    m_fov((45.)*(PI/180.)),
	m_focalLength(1),
	m_uncertaintyRadius(0)
{
    calcLookAt();
}

Camera::~Camera()
{

}

void Camera::click(Scene* pScene, HdrImage* pImage)
{
    calcLookAt();
    static bool firstRayTrace = false;

    if (m_renderer == RENDER_OPENGL)
    {
        glDrawBuffer(GL_BACK);
        pScene->openGL(this);
        firstRayTrace = true;
    }
    else if (m_renderer == RENDER_RAYTRACE)
    {
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        glDrawBuffer(GL_FRONT);
        if (firstRayTrace)
        {
            pImage->clear(bgColor());
            pScene->raytraceImage(this, g_hdrImage);
            firstRayTrace = false;
        }
        
        g_hdrImage->draw();
    }
	else if (m_renderer == RENDER_PATHTRACE)
	{
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        glDrawBuffer(GL_FRONT);
        if (firstRayTrace)
        {
            pImage->clear(bgColor());
            pScene->pathtraceImage(this, g_hdrImage);
            firstRayTrace = false;
        }
        
        g_hdrImage->draw();
	}
	else if (m_renderer == RENDER_METROPOLIS)
	{
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        glDrawBuffer(GL_FRONT);
        if (firstRayTrace)
        {
            pImage->clear(bgColor());
            pScene->metropolisImage(this, g_hdrImage);
            firstRayTrace = false;
        }

        g_hdrImage->draw();
	}
	else if (m_renderer == RENDER_APPM)
	{
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        glDrawBuffer(GL_FRONT);
        if (firstRayTrace)
        {
            pImage->clear(bgColor());
            pScene->appmImage(this, g_hdrImage);
            firstRayTrace = false;
        }

        g_hdrImage->draw();
	}
}

void Camera::calcLookAt()
{
    // this is true when a "lookat" is not used in the config file
    if (m_lookAt.x != FLT_MAX)
    {
        setLookAt(m_lookAt);
        m_lookAt.set(FLT_MAX, FLT_MAX, FLT_MAX);
    }
}

void Camera::drawGL()
{
    // set up the screen with our camera parameters
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(fov(), g_image->width()/(float)g_image->height(),
                   0.01, 10000);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    Vector3 vCenter = eye() + viewDir();
    gluLookAt(eye().x, eye().y, eye().z,
              vCenter.x, vCenter.y, vCenter.z,
              up().x, up().y, up().z);
}

Ray Camera::eyeRay(int x, int y, int imageWidth, int imageHeight)
{
    // first compute the camera coordinate system 
    // ------------------------------------------

    // wDir = e - (e+m_viewDir) = -m_vView
    const Vector3 wDir = Vector3(-m_viewDir).normalize(); 
    const Vector3 uDir = cross(m_up, wDir).normalize(); 
    const Vector3 vDir = cross(wDir, uDir);    



    // next find the corners of the image plane in camera space
    // --------------------------------------------------------

    const float aspectRatio = (float)imageWidth/(float)imageHeight; 


    const float top     = tan(m_fov*HalfDegToRad); 
    const float right   = aspectRatio*top; 

    const float bottom  = -top; 
    const float left    = -right; 



    // transform x and y into camera space 
    // -----------------------------------

    const float imPlaneUPos = left   + (right - left)*(((float)x+0.5f)/(float)imageWidth); 
    const float imPlaneVPos = bottom + (top - bottom)*(((float)y+0.5f)/(float)imageHeight); 

    return Ray(m_eye, (imPlaneUPos*uDir + imPlaneVPos*vDir - wDir).normalize());
}

Ray Camera::specificEyeRay(float x, float y, int imageWidth, int imageHeight)
{
    // first compute the camera coordinate system 
    // ------------------------------------------

    // wDir = e - (e+m_viewDir) = -m_vView
    const Vector3 wDir = Vector3(-m_viewDir).normalize(); 
    const Vector3 uDir = cross(m_up, wDir).normalize(); 
    const Vector3 vDir = cross(wDir, uDir);    



    // next find the corners of the image plane in camera space
    // --------------------------------------------------------

    const float aspectRatio = (float)imageWidth/(float)imageHeight; 


    const float top     = tan(m_fov*HalfDegToRad); 
    const float right   = aspectRatio*top; 

    const float bottom  = -top; 
    const float left    = -right; 



    // transform x and y into camera space 
    // -----------------------------------

    const float imPlaneUPos = left   + (right - left)*(x/(float)imageWidth); 
    const float imPlaneVPos = bottom + (top - bottom)*(y/(float)imageHeight); 

    return Ray(m_eye, (imPlaneUPos*uDir + imPlaneVPos*vDir - wDir).normalize());
}

Ray Camera::randEyeRay(int x, int y, int imageWidth, int imageHeight)
{
    // first compute the camera coordinate system 
    // ------------------------------------------

    // wDir = e - (e+m_viewDir) = -m_vView
    const Vector3 wDir = Vector3(-m_viewDir).normalize(); 
    const Vector3 uDir = cross(m_up, wDir).normalize(); 
    const Vector3 vDir = cross(wDir, uDir);    



    // next find the corners of the image plane in camera space
    // --------------------------------------------------------

    const float aspectRatio = (float)imageWidth/(float)imageHeight; 


    const float top     = tan(m_fov*HalfDegToRad); 
    const float right   = aspectRatio*top; 

    const float bottom  = -top; 
    const float left    = -right; 



    // transform x and y into camera space 
    // -----------------------------------
	

	// pick a random point within the pixel at x,y
	float dx = (1.*fabs(rand()))/RAND_MAX;
	float dy = (1.*fabs(rand()))/RAND_MAX;

	const float imPlaneUPos = left   + (right - left)*(((float)x+dx)/(float)imageWidth); 
	const float imPlaneVPos = bottom + (top - bottom)*(((float)y+dy)/(float)imageHeight);

	Ray ray(m_eye, (imPlaneUPos*uDir + imPlaneVPos*vDir - wDir).normalize());

	if (m_uncertaintyRadius < epsilon) // no depth of field
	{
		return ray;
	}
	else // depth of field
	{
		// simulate depth of field by jittering the origin

		// find the ray's intersection with the focal plane
		// TODO: remember everything's not necessarily axis aligned -> must use basis
		// for now, just use t = focal length (this makes a focal sphere, not plane)
		Vector3 p = ray.o + m_focalLength*ray.d;

		// then jitter the eye
		// TODO: eventually I'd prefer if the uncertainty radius is an angle from unblurred
		// viewing ray, centered at focal point
		float theta = (PI/2*fabs(rand()))/RAND_MAX;
		float phi = (2*PI*fabs(rand()))/RAND_MAX;
		float dx = m_uncertaintyRadius*cos(theta)*cos(phi);
		float dy = m_uncertaintyRadius*cos(theta)*sin(phi);
		Vector3 eye_blur = m_eye + dx*uDir + dy*vDir;

		// and find new direction
		return Ray(eye_blur, (p - eye_blur).normalize());
	}
}
